/*
 * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package java.lang.invoke;

import java.lang.reflect.*;
import java.security.AccessController;
import java.security.PrivilegedAction;
import sun.invoke.WrapperInstance;
import java.util.ArrayList;
import sun.reflect.CallerSensitive;
import sun.reflect.Reflection;
import sun.reflect.misc.ReflectUtil;

import static java.lang.invoke.MethodHandleStatics.*;

/**
 * This class consists exclusively of static methods that help adapt
 * method handles to other JVM types, such as interfaces.
 */
public class MethodHandleProxies {

  private MethodHandleProxies() {
  }  // do not instantiate

  /**
   * Produces an instance of the given single-method interface which redirects
   * its calls to the given method handle.
   * <p>
   * A single-method interface is an interface which declares a uniquely named method.
   * When determining the uniquely named method of a single-method interface,
   * the public {@code Object} methods ({@code toString}, {@code equals}, {@code hashCode})
   * are disregarded.  For example, {@link java.util.Comparator} is a single-method interface,
   * even though it re-declares the {@code Object.equals} method.
   * <p>
   * The interface must be public.  No additional access checks are performed.
   * <p>
   * The resulting instance of the required type will respond to
   * invocation of the type's uniquely named method by calling
   * the given target on the incoming arguments,
   * and returning or throwing whatever the target
   * returns or throws.  The invocation will be as if by
   * {@code target.invoke}.
   * The target's type will be checked before the
   * instance is created, as if by a call to {@code asType},
   * which may result in a {@code WrongMethodTypeException}.
   * <p>
   * The uniquely named method is allowed to be multiply declared,
   * with distinct type descriptors.  (E.g., it can be overloaded,
   * or can possess bridge methods.)  All such declarations are
   * connected directly to the target method handle.
   * Argument and return types are adjusted by {@code asType}
   * for each individual declaration.
   * <p>
   * The wrapper instance will implement the requested interface
   * and its super-types, but no other single-method interfaces.
   * This means that the instance will not unexpectedly
   * pass an {@code instanceof} test for any unrequested type.
   * <p style="font-size:smaller;">
   * <em>Implementation Note:</em>
   * Therefore, each instance must implement a unique single-method interface.
   * Implementations may not bundle together
   * multiple single-method interfaces onto single implementation classes
   * in the style of {@link java.awt.AWTEventMulticaster}.
   * <p>
   * The method handle may throw an <em>undeclared exception</em>,
   * which means any checked exception (or other checked throwable)
   * not declared by the requested type's single abstract method.
   * If this happens, the throwable will be wrapped in an instance of
   * {@link java.lang.reflect.UndeclaredThrowableException UndeclaredThrowableException}
   * and thrown in that wrapped form.
   * <p>
   * Like {@link java.lang.Integer#valueOf Integer.valueOf},
   * {@code asInterfaceInstance} is a factory method whose results are defined
   * by their behavior.
   * It is not guaranteed to return a new instance for every call.
   * <p>
   * Because of the possibility of {@linkplain java.lang.reflect.Method#isBridge bridge methods}
   * and other corner cases, the interface may also have several abstract methods
   * with the same name but having distinct descriptors (types of returns and parameters).
   * In this case, all the methods are bound in common to the one given target.
   * The type check and effective {@code asType} conversion is applied to each
   * method type descriptor, and all abstract methods are bound to the target in common.
   * Beyond this type check, no further checks are made to determine that the
   * abstract methods are related in any way.
   * <p>
   * Future versions of this API may accept additional types,
   * such as abstract classes with single abstract methods.
   * Future versions of this API may also equip wrapper instances
   * with one or more additional public "marker" interfaces.
   * <p>
   * If a security manager is installed, this method is caller sensitive.
   * During any invocation of the target method handle via the returned wrapper,
   * the original creator of the wrapper (the caller) will be visible
   * to context checks requested by the security manager.
   *
   * @param <T> the desired type of the wrapper, a single-method interface
   * @param intfc a class object representing {@code T}
   * @param target the method handle to invoke from the wrapper
   * @return a correctly-typed wrapper for the given target
   * @throws NullPointerException if either argument is null
   * @throws IllegalArgumentException if the {@code intfc} is not a valid argument to this method
   * @throws WrongMethodTypeException if the target cannot be converted to the type required by the
   * requested interface
   */
  // Other notes to implementors:
  // <p>
  // No stable mapping is promised between the single-method interface and
  // the implementation class C.  Over time, several implementation
  // classes might be used for the same type.
  // <p>
  // If the implementation is able
  // to prove that a wrapper of the required type
  // has already been created for a given
  // method handle, or for another method handle with the
  // same behavior, the implementation may return that wrapper in place of
  // a new wrapper.
  // <p>
  // This method is designed to apply to common use cases
  // where a single method handle must interoperate with
  // an interface that implements a function-like
  // API.  Additional variations, such as single-abstract-method classes with
  // private constructors, or interfaces with multiple but related
  // entry points, must be covered by hand-written or automatically
  // generated adapter classes.
  //
  @CallerSensitive
  public static <T> T asInterfaceInstance(final Class<T> intfc, final MethodHandle target) {
    if (!intfc.isInterface() || !Modifier.isPublic(intfc.getModifiers())) {
      throw newIllegalArgumentException("not a public interface", intfc.getName());
    }
    final MethodHandle mh;
    if (System.getSecurityManager() != null) {
      final Class<?> caller = Reflection.getCallerClass();
      final ClassLoader ccl = caller != null ? caller.getClassLoader() : null;
      ReflectUtil.checkProxyPackageAccess(ccl, intfc);
      mh = ccl != null ? bindCaller(target, caller) : target;
    } else {
      mh = target;
    }
    ClassLoader proxyLoader = intfc.getClassLoader();
    if (proxyLoader == null) {
      ClassLoader cl = Thread.currentThread().getContextClassLoader(); // avoid use of BCP
      proxyLoader = cl != null ? cl : ClassLoader.getSystemClassLoader();
    }
    final Method[] methods = getSingleNameMethods(intfc);
    if (methods == null) {
      throw newIllegalArgumentException("not a single-method interface", intfc.getName());
    }
    final MethodHandle[] vaTargets = new MethodHandle[methods.length];
    for (int i = 0; i < methods.length; i++) {
      Method sm = methods[i];
      MethodType smMT = MethodType.methodType(sm.getReturnType(), sm.getParameterTypes());
      MethodHandle checkTarget = mh.asType(smMT);  // make throw WMT
      checkTarget = checkTarget.asType(checkTarget.type().changeReturnType(Object.class));
      vaTargets[i] = checkTarget.asSpreader(Object[].class, smMT.parameterCount());
    }
    final InvocationHandler ih = new InvocationHandler() {
      private Object getArg(String name) {
        if ((Object) name == "getWrapperInstanceTarget") {
          return target;
        }
        if ((Object) name == "getWrapperInstanceType") {
          return intfc;
        }
        throw new AssertionError();
      }

      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        for (int i = 0; i < methods.length; i++) {
          if (method.equals(methods[i])) {
            return vaTargets[i].invokeExact(args);
          }
        }
        if (method.getDeclaringClass() == WrapperInstance.class) {
          return getArg(method.getName());
        }
        if (isObjectMethod(method)) {
          return callObjectMethod(proxy, method, args);
        }
        throw newInternalError("bad proxy method: " + method);
      }
    };

    final Object proxy;
    if (System.getSecurityManager() != null) {
      // sun.invoke.WrapperInstance is a restricted interface not accessible
      // by any non-null class loader.
      final ClassLoader loader = proxyLoader;
      proxy = AccessController.doPrivileged(new PrivilegedAction<Object>() {
        public Object run() {
          return Proxy.newProxyInstance(
              loader,
              new Class<?>[]{intfc, WrapperInstance.class},
              ih);
        }
      });
    } else {
      proxy = Proxy.newProxyInstance(proxyLoader,
          new Class<?>[]{intfc, WrapperInstance.class},
          ih);
    }
    return intfc.cast(proxy);
  }

  private static MethodHandle bindCaller(MethodHandle target, Class<?> hostClass) {
    MethodHandle cbmh = MethodHandleImpl.bindCaller(target, hostClass);
    if (target.isVarargsCollector()) {
      MethodType type = cbmh.type();
      int arity = type.parameterCount();
      return cbmh.asVarargsCollector(type.parameterType(arity - 1));
    }
    return cbmh;
  }

  /**
   * Determines if the given object was produced by a call to {@link #asInterfaceInstance
   * asInterfaceInstance}.
   *
   * @param x any reference
   * @return true if the reference is not null and points to an object produced by {@code
   * asInterfaceInstance}
   */
  public static boolean isWrapperInstance(Object x) {
    return x instanceof WrapperInstance;
  }

  private static WrapperInstance asWrapperInstance(Object x) {
    try {
      if (x != null) {
        return (WrapperInstance) x;
      }
    } catch (ClassCastException ex) {
    }
    throw newIllegalArgumentException("not a wrapper instance");
  }

  /**
   * Produces or recovers a target method handle which is behaviorally equivalent to the unique
   * method of this wrapper instance. The object {@code x} must have been produced by a call to
   * {@link #asInterfaceInstance asInterfaceInstance}. This requirement may be tested via {@link
   * #isWrapperInstance isWrapperInstance}.
   *
   * @param x any reference
   * @return a method handle implementing the unique method
   * @throws IllegalArgumentException if the reference x is not to a wrapper instance
   */
  public static MethodHandle wrapperInstanceTarget(Object x) {
    return asWrapperInstance(x).getWrapperInstanceTarget();
  }

  /**
   * Recovers the unique single-method interface type for which this wrapper instance was created.
   * The object {@code x} must have been produced by a call to {@link #asInterfaceInstance
   * asInterfaceInstance}. This requirement may be tested via {@link #isWrapperInstance
   * isWrapperInstance}.
   *
   * @param x any reference
   * @return the single-method interface type for which the wrapper was created
   * @throws IllegalArgumentException if the reference x is not to a wrapper instance
   */
  public static Class<?> wrapperInstanceType(Object x) {
    return asWrapperInstance(x).getWrapperInstanceType();
  }

  private static boolean isObjectMethod(Method m) {
    switch (m.getName()) {
      case "toString":
        return (m.getReturnType() == String.class
            && m.getParameterTypes().length == 0);
      case "hashCode":
        return (m.getReturnType() == int.class
            && m.getParameterTypes().length == 0);
      case "equals":
        return (m.getReturnType() == boolean.class
            && m.getParameterTypes().length == 1
            && m.getParameterTypes()[0] == Object.class);
    }
    return false;
  }

  private static Object callObjectMethod(Object self, Method m, Object[] args) {
    assert (isObjectMethod(m)) : m;
    switch (m.getName()) {
      case "toString":
        return self.getClass().getName() + "@" + Integer.toHexString(self.hashCode());
      case "hashCode":
        return System.identityHashCode(self);
      case "equals":
        return (self == args[0]);
    }
    return null;
  }

  private static Method[] getSingleNameMethods(Class<?> intfc) {
    ArrayList<Method> methods = new ArrayList<Method>();
    String uniqueName = null;
    for (Method m : intfc.getMethods()) {
      if (isObjectMethod(m)) {
        continue;
      }
      if (!Modifier.isAbstract(m.getModifiers())) {
        continue;
      }
      String mname = m.getName();
      if (uniqueName == null) {
        uniqueName = mname;
      } else if (!uniqueName.equals(mname)) {
        return null;  // too many abstract methods
      }
      methods.add(m);
    }
    if (uniqueName == null) {
      return null;
    }
    return methods.toArray(new Method[methods.size()]);
  }
}
